////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2008 Michiel Helvensteijn                                    //
//                                                                            //
// This file is part of the Mist compiler.                                    //
//                                                                            //
// The Mist compiler is free software: you can redistribute it and/or modif   //
// it under the terms of the GNU General Public License as published by       //
// the Free Software Foundation, either version 3 of the License, or          //
// (at your option) any later version.                                        //
//                                                                            //
// The Mist compiler is distributed in the hope that it will be useful,       //
// but WITHOUT ANY WARRANTY; without even the implied warranty of             //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the              //
// GNU General Public License for more details.                               //
//                                                                            //
// You should have received a copy of the GNU General Public License          //
// along with the Mist compiler.  If not, see <http://www.gnu.org/licenses/>. //
////////////////////////////////////////////////////////////////////////////////

#include "visitors/typevisitor.hpp"

/******************************************************************************
 * Includes                                                                   *
 ******************************************************************************/

#include <boost/foreach.hpp>
#define foreach BOOST_FOREACH

#include <boost/format.hpp>
using boost::format;

#include <vector>
using std::vector;

#include <string>
using std::string;

#include <boost/shared_ptr.hpp>
using boost::dynamic_pointer_cast;

#include <typeinfo>

#include "ast/nodetypes.hpp"
#include "visitors/visitor.hpp"
#include "visitors/echovisitor.hpp"
#include "visitors/expressionnavigator.hpp"
#include "messages.hpp"
#include "common.hpp"
#include "assert.hpp"
#include "symboltree.hpp"

/******************************************************************************
 * TypeVisitor Class                                                          *
 ******************************************************************************/

class TypeVisitor : public ExpressionNavigator {
	public:
		
		TypeVisitor(shared_ptr<Messages> messages) : ExpressionNavigator(messages) {}
		
		void visit(shared_ptr<ArithmeticOperation>   node);
		void visit(shared_ptr<FunctionCall>          node);
		void visit(shared_ptr<UnaryOperation>        node);
		void visit(shared_ptr<IntegerLiteral>        node);
		void visit(shared_ptr<BooleanLiteral>        node);
		void visit(shared_ptr<Null>                  node);
		void visit(shared_ptr<Assertion>             node);
		void visit(shared_ptr<IfStatement>           node);
		void visit(shared_ptr<WhileStatement>        node);
		void visit(shared_ptr<Assignment>            node);
		void visit(shared_ptr<IdAssignment>          node);
		void visit(shared_ptr<VariableDeclaration>   node);
		void visit(shared_ptr<FunctionDeclaration>   node);
		void visit(shared_ptr<Subscripting>          node);
		void visit(shared_ptr<ComparisonChain>       node);
		void visit(shared_ptr<AndChain>              node);
		void visit(shared_ptr<OrChain>               node);
		void visit(shared_ptr<ImpliesChain>          node);
		void visit(shared_ptr<ForStatement>          node);
		void visit(shared_ptr<Range>                 node);
		void visit(shared_ptr<Reference>             node);
		void visit(shared_ptr<BooleanPointerLiteral> node);
		void visit(shared_ptr<IntegerPointerLiteral> node);
};

/******************************************************************************
 * Function Implementations                                                   *
 ******************************************************************************/

void test_types(shared_ptr<Node> node, shared_ptr<Messages> messages) {
	assert(node);
	
	shared_ptr<TypeVisitor> visitor(new TypeVisitor(messages));
	
	node->accept(visitor);
}

/******************************************************************************/

void TypeVisitor::visit(shared_ptr<ArithmeticOperation> node) {
	assert(node);
	
	ExpressionNavigator::visit(node);
	
	if (!node->left_operand()->type()->equals(IntegerType()) && !node->left_operand()->type()->equals(TypeError())) {
		messages()->add("wrong-type", node->left_operand()->location())
			% (format("the left side of the %1% operator")
				% to_string(node->operator_type()))
			% echo(node->left_operand()->type())
			% "int";
	}
	
	if (!node->right_operand()->type()->equals(IntegerType()) && !node->right_operand()->type()->equals(TypeError())) {
		messages()->add("wrong-type", node->right_operand()->location())
			% (format("the right side of the %1% operator")
				% to_string(node->operator_type()))
			% echo(node->right_operand()->type())
			% "int";
	}
	
	node->set_type(IntegerType::create());
}

void TypeVisitor::visit(shared_ptr<FunctionCall> node) {
	assert(node);
	
	ExpressionNavigator::visit(node);
	
	if (typeid(*node->base()->type()) == typeid(FunctionType)) {
		shared_ptr<FunctionType> ftype(dynamic_pointer_cast<FunctionType>(node->base()->type()));
		
		bool parameterTypesCorrect = (node->parameter_count() == ftype->parameter_type_count());
		for (unsigned i = 0; i < node->parameter_count() && parameterTypesCorrect; ++i)
			if (!node->parameters()[i]->type()->equals(ftype->parameter_types()[i].type))
				parameterTypesCorrect = false;
		
		if (!parameterTypesCorrect) {
			vector<string> usedParamTypes;
			vector<string> neededParamTypes;
			
			bool error = false;
			foreach (shared_ptr<Expression> parameter, node->parameters()) {
				usedParamTypes.push_back(echo(parameter->type()));
				if (parameter->type()->equals(TypeError()))
					error = true;
			}
			
			foreach (ParameterType paramType, ftype->parameter_types())
				neededParamTypes.push_back(echo(paramType.type));
			
			if (!error) {
				messages()->add("wrong-types", node->location())
					% "the parameter-types of the function-call"
					% ("(" + implode(usedParamTypes  , ", ") + ")")
					% ("(" + implode(neededParamTypes, ", ") + ")");
			}
		} else { // Maybe still passing a weak/strong to an alias strong/weak
			for (unsigned i = 0; i < node->parameter_count(); ++i) {
				if (ftype->parameter_types()[i].policy->identityReturned) { // pass by als
					if (   typeid(*node->parameters()[i]) == typeid(Reference)
					    || typeid(*node->parameters()[i]) == typeid(Subscripting)) {
						if (   node->parameters()[i]->type()->weak_pointer() == true
						    && ftype->parameter_types()[i].type->weak_pointer() == false) {
							messages()->add("a-weak-f-strong", node->parameters()[i]->location()) % ordinal(i + 1);
						} else if (   node->parameters()[i]->type()->weak_pointer() == true
						           && ftype->parameter_types()[i].type->weak_pointer() == false) {
							messages()->add("a-strong-f-weak", node->parameters()[i]->location()) % ordinal(i + 1);
						}
					}
				}
			}
		}
		node->set_type(ftype->return_type());
	} else {
		if (!node->base()->type()->equals(TypeError())) {
			messages()->add("not-function-type", node->base()->location())
				% echo(node->base()->type());
		}
		node->set_type(TypeError::create());
	}
}

void TypeVisitor::visit(shared_ptr<UnaryOperation> node) {
	assert(node);
	
	ExpressionNavigator::visit(node);
	
	switch(node->operator_type()) {
		case NotOperator: {
			if (   !node->operand()->type()->equals(BooleanType())
			    && !node->operand()->type()->equals(TypeError())) {
				messages()->add("wrong-type", node->operand()->location())
					% (format("the operand of the %1% operator")
						% to_string(node->operator_type()))
					% echo(node->operand()->type())
					% "bool";
			}
			node->set_type(BooleanType::create());
			break;
		}
		
		case UnaryMinusOperator: {
			if (   !node->operand()->type()->equals(IntegerType())
			    && !node->operand()->type()->equals(TypeError())) {
				messages()->add("wrong-type", node->operand()->location())
					% (format("the operand of the %1% operator")
						% to_string(node->operator_type()))
					% echo(node->operand()->type())
					% "int";
			}
			node->set_type(IntegerType::create());
			break;
		}
		
		case CountOperator: {
			if (typeid(*node->operand()->type()) != typeid(ArrayType)
			    && !node->operand()->type()->equals(TypeError())) {
				messages()->add("not-array-type", node->operand()->location())
					% echo(node->operand()->type());
			}
			node->set_type(IntegerType::create());
			break;
		}
	}
}

void TypeVisitor::visit(shared_ptr<IntegerLiteral> node) {
	assert(node);
	
	node->set_type(IntegerType::create());
}

void TypeVisitor::visit(shared_ptr<BooleanLiteral> node) {
	assert(node);
	
	node->set_type(BooleanType::create());
}

void TypeVisitor::visit(shared_ptr<Null> node) {
	assert(node);
	
	node->set_type(TypeError::create());
}

void TypeVisitor::visit(shared_ptr<Assertion> node) {
	assert(node);
	
	ExpressionNavigator::visit(node);
	
	if (node->condition_is_set()) {
		if (   !node->condition()->type()->equals(BooleanType())
			&& !node->condition()->type()->equals(TypeError())) {
			messages()->add("wrong-type", node->location())
				% "the assertion"
				% echo(node->condition()->type())
				% "bool";
		}
	} else {
		shared_ptr<Type> type = node->variable_declaration()->type();
		bool weakFound = false;
		CodeLocation location;
		do {
			if (type->weak_pointer()) {
				if (!weakFound)
					location = type->location();
				weakFound = true;
				break;
			}
			if (typeid(*type) == typeid(ArrayType))
				type = dynamic_pointer_cast<ArrayType>(type)->content_type();
		} while (typeid(*type) == typeid(ArrayType));
		
		if (weakFound)
			messages()->add("weak-logical-var", location);
	}
}

void TypeVisitor::visit(shared_ptr<IfStatement> node) {
	assert(node);
	
	node->condition()->accept(shared_from_this());
	
	if (   !node->condition()->type()->equals(BooleanType())
	    && !node->condition()->type()->equals(TypeError())) {
		messages()->add("wrong-type", node->condition()->location())
			% "the if-statement condition"
			% echo(node->condition()->type())
			% "bool";
	}
	
	StatementNavigator::visit(node);
}

void TypeVisitor::visit(shared_ptr<WhileStatement> node) {
	assert(node);
	
	node->condition()->accept(shared_from_this());
	
	if (   !node->condition()->type()->equals(BooleanType())
	    && !node->condition()->type()->equals(TypeError())) {
		messages()->add("wrong-type", node->condition()->location())
			% "the while-statement condition"
			% echo(node->condition()->type())
			% "bool";
	}
	
	node->statement()->accept(shared_from_this());
}

void TypeVisitor::visit(shared_ptr<Assignment> node) {
	assert(node);
	
	node->left_side()->accept(shared_from_this());
	node->right_side()->accept(shared_from_this());
	
	if (   !node-> left_side()->type()->equals(node->right_side()->type())
	    && !node-> left_side()->type()->equals(TypeError())
	    && !node->right_side()->type()->equals(TypeError())) {
		messages()->add("type-mismatch", node->location())
			% "the left side of the assignment"
			% echo(node->left_side()->type())
			% "the right side of the assignment"
			% echo(node->right_side()->type());
	}
	node->set_type(node->left_side()->type());
}

void TypeVisitor::visit(shared_ptr<IdAssignment> node) {
	assert(node);
	
	node->left_side()->accept(shared_from_this());
	node->right_side()->accept(shared_from_this());
	
	if (   !node-> left_side()->type()->equals(node->right_side()->type())
	    && !node-> left_side()->type()->equals(TypeError())
	    && !node->right_side()->type()->equals(TypeError())) {
		messages()->add("type-mismatch", node->location())
			% "the left side of the identity assignment"
			% echo(node->left_side()->type())
			% "the right side of the identity assignment"
			% echo(node->right_side()->type());
	}
	node->set_type(node->left_side()->type());
}

void TypeVisitor::visit(shared_ptr<VariableDeclaration> node) {
	assert(node);
	
	ExpressionNavigator::visit(node);
	
	foreach (VarDec var, node->variables()) {
		if (var.is_initialized()) {
			if (!var.init->type()->equals(node->type()) && !var.init->type()->equals(TypeError())) {
				messages()->add("type-mismatch", CodeLocation(var.name->location().begin(), var.init->location().end()))
					% (format("the variable %1%") % var.name->text())
					% echo(node->type())
					% "its initialization"
					% echo(var.init->type());
			}
		}
	}
}

void TypeVisitor::visit(shared_ptr<FunctionDeclaration> node) {
	assert(node);
	
	StatementNavigator::visit(node);
	
	shared_ptr<FunctionType> ftype = node->type();
	
	for (unsigned i = 0; i < node->parameter_name_count(); ++i) {
		if (ftype->parameter_types()[i].policy->identityPassed == false && ftype->parameter_types()[i].policy->formalAccess.identity.readable
		    && ftype->parameter_types()[i].type->weak_pointer())
			messages()->add("val-weak-pointer", CodeLocation(ftype->parameter_types()[i].type->location().begin(), node->parameter_names()[i]->location().end()))
				% node->parameter_names()[i]->text();
	}
}

void TypeVisitor::visit(shared_ptr<Subscripting> node) {
	assert(node);
	
	ExpressionNavigator::visit(node);
	
	if (node->base()->type()->equals(TypeError())) {
		node->set_type(TypeError::create());
	} else if (typeid(*node->base()->type()) != typeid(ArrayType)) {
		messages()->add("not-array-type", node->base()->location())
			% echo(node->base()->type());
		
		node->set_type(TypeError::create());
	} else {
		node->set_type(dynamic_pointer_cast<ArrayType>(node->base()->type())->content_type());
	}
	
	if (!node->subscript()->type()->equals(IntegerType()) && !node->subscript()->type()->equals(TypeError())) {
		messages()->add("wrong-type", node->subscript()->location())
			% "the subscript"
			% echo(node->base()->type())
			% "int";
	}
}

void TypeVisitor::visit(shared_ptr<ComparisonChain> node) {
	assert(node);
	
	ExpressionNavigator::visit(node);
	
	unsigned index = 1;
	foreach (Comparison comparison, node->comparisons()) {
		if (!comparison.left_operand->type()->equals(TypeError()) && !comparison.right_operand->type()->equals(TypeError())) {
			if (!comparison.left_operand->type()->equals(comparison.right_operand->type())) {
				messages()->add("type-mismatch", CodeLocation(comparison.left_operand->location().begin(), comparison.right_operand->location().end()))
					% (format("%1% operand of %2%")
						% ordinal(index)
						% (node->operand_count() > 2 ? "comparison-chain" : "comparison"))
					% echo(comparison.left_operand->type())
					% (format("%1% operand of %2%")
						% ordinal(index + 1)
						% (node->operand_count() > 2 ? "comparison-chain" : "comparison"))
					% echo(comparison.right_operand->type());
			} else if (comparison.left_operand->type()->equals(BooleanType())) {
				if (comparison.comparison_operator == LessThanOperator
				    || comparison.comparison_operator == GreaterThanOperator
				    || comparison.comparison_operator == UnequalOperator) {
					messages()->add("predictable-return", CodeLocation(comparison.left_operand->location().begin(), comparison.right_operand->location().end()))
						% (format("the %1% operator used on `bool's") % to_string(comparison.comparison_operator))
						% "false";
				} else if (comparison.comparison_operator == NotLessOperator
				           || comparison.comparison_operator == NotGreaterOperator
				           || comparison.comparison_operator == NotUnequalOperator) {
					messages()->add("predictable-return", CodeLocation(comparison.left_operand->location().begin(), comparison.right_operand->location().end()))
						% (format("the %1% operator used on `bool's") % to_string(comparison.comparison_operator))
						% "true";
				}
			} else if (comparison.left_operand->type()->equals(IntegerType())) {
				if (comparison.comparison_operator == ComparableOperator) {
					messages()->add("predictable-return", CodeLocation(comparison.left_operand->location().begin(), comparison.right_operand->location().end()))
						% (format("the %1% operator used on `int's") % to_string(comparison.comparison_operator))
						% "true";
				} else if (comparison.comparison_operator == NotComparableOperator) {
					messages()->add("predictable-return", CodeLocation(comparison.left_operand->location().begin(), comparison.right_operand->location().end()))
						% (format("the %1% operator used on `int's") % to_string(comparison.comparison_operator))
						% "false";
				}
			} else if (typeid(*comparison.left_operand->type()) == typeid(ArrayType) && comparison.comparison_operator !=    IdentityOperator
			                                                                         && comparison.comparison_operator != NotIdentityOperator) {
				messages()->add("array-compare", CodeLocation(comparison.left_operand->location().begin(), comparison.right_operand->location().end()));
			} else if (typeid(*comparison.left_operand->type()) == typeid(FunctionType)) {
				messages()->add("function-compare", CodeLocation(comparison.left_operand->location().begin(), comparison.right_operand->location().end()));
			} else if (typeid(*comparison.left_operand->type()) == typeid(VoidType)) {
				messages()->add("void-compare", CodeLocation(comparison.left_operand->location().begin(), comparison.right_operand->location().end()));
			}
		}
		++index;
	}
	
	node->set_type(BooleanType::create());
}

void TypeVisitor::visit(shared_ptr<AndChain> node) {
	assert(node);
	
	ExpressionNavigator::visit(node);
	
	unsigned number = 1;
	foreach (shared_ptr<Expression> operand, node->operands()) {
		if (!operand->type()->equals(BooleanType()) && !operand->type()->equals(TypeError())) {
			messages()->add("wrong-type", operand->location())
				% (format("the %1% operand of the logical conjunction") % ordinal(number))
				% echo(operand->type())
				% "bool";
		}
		++number;
	}
	
	node->set_type(BooleanType::create());
}

void TypeVisitor::visit(shared_ptr<OrChain> node) {
	assert(node);
	
	ExpressionNavigator::visit(node);
	
	unsigned number = 1;
	foreach (shared_ptr<Expression> operand, node->operands()) {
		if (!operand->type()->equals(BooleanType()) && !operand->type()->equals(TypeError())) {
			messages()->add("wrong-type", operand->location())
				% (format("the %1% operand of the logical disjunction") % ordinal(number))
				% echo(operand->type())
				% "bool";
		}
		++number;
	}
	
	node->set_type(BooleanType::create());
}

void TypeVisitor::visit(shared_ptr<ImpliesChain> node) {
	assert(node);
	
	ExpressionNavigator::visit(node);
	
	unsigned number = 1;
	foreach (shared_ptr<Expression> operand, node->operands()) {
		if (!operand->type()->equals(BooleanType()) && !operand->type()->equals(TypeError())) {
			messages()->add("wrong-type", operand->location())
				% (format("the %1% operand of the %2%")
					% ordinal(number)
					% (node->operand_count() > 2 ? "implication-chain" : "implication"))
				% echo(operand->type())
				% "bool";
		}
		++number;
	}
	
	node->set_type(BooleanType::create());
}

void TypeVisitor::visit(shared_ptr<ForStatement> node) {
	assert(node);
	
	node->range()->accept(shared_from_this());
	
	if (   !node->range()->type()->equals(RangeType(IntegerType::create()))
	    && !node->range()->type()->equals(RangeType(IntegerType::create()))
	    && !node->range()->type()->equals(TypeError())) {
		messages()->add("wrong-type", node->range()->location())
			% "the for-loop range"
			% echo(node->range()->type())
			% "int..";
	}
	
	node->statement()->accept(shared_from_this());
}

void TypeVisitor::visit(shared_ptr<Range> node) {
	assert(node);
	
	node->lower_bound()->accept(shared_from_this());
	node->upper_bound()->accept(shared_from_this());
	node->step()->accept(shared_from_this());
	
	if (   !node->lower_bound()->type()->equals(IntegerType())
	    && !node->lower_bound()->type()->equals(TypeError())) {
		messages()->add("wrong-type", node->lower_bound()->location())
			% "the lower-bound of the range"
			% echo(node->lower_bound()->type())
			% "int";
	}
	
	if (   !node->upper_bound()->type()->equals(IntegerType())
	    && !node->upper_bound()->type()->equals(TypeError())) {
		messages()->add("wrong-type", node->upper_bound()->location())
			% "the upper_bound of the range"
			% echo(node->upper_bound()->type())
			% "int";
	}
	
	if (   !node->step()->type()->equals(IntegerType())
	    && !node->step()->type()->equals(TypeError())) {
		messages()->add("wrong-type", node->step()->location())
			% "the step-size of the range"
			% echo(node->step()->type())
			% "int";
	}
	
	node->set_type(shared_ptr<RangeType>(RangeType::create(node->lower_bound()->type())));
}

void TypeVisitor::visit(shared_ptr<Reference> node) {
	assert(node);
	
	if (node->symbol_is_set()) {
		node->set_type(node->symbol()->type());
	} else {
		node->set_type(TypeError::create());
	}
}

void TypeVisitor::visit(shared_ptr<BooleanPointerLiteral> node) {
	assert(node);
	
	ExpressionNavigator::visit(node);
	
	if (   !node->value()->type()->equals(BooleanType())
	    && !node->value()->type()->equals(TypeError())) {
		messages()->add("wrong-type", node->value()->location())
			% "the value of the boolean pointer literal"
			% echo(node->value()->type())
			% "bool";
	}
	
	node->set_type(BooleanType::create());
}

void TypeVisitor::visit(shared_ptr<IntegerPointerLiteral> node) {
	assert(node);
	
	ExpressionNavigator::visit(node);
	
	if (   !node->value()->type()->equals(IntegerType())
	    && !node->value()->type()->equals(TypeError())) {
		messages()->add("wrong-type", node->value()->location())
			% "the value of the integer pointer literal"
			% echo(node->value()->type())
			% "int";
	}
	
	node->set_type(IntegerType::create());
}
